package com.xinqi.modules.exam.utils;

import com.baomidou.mybatisplus.annotation.TableField;
import com.google.common.collect.ImmutableList;
import com.xinqi.common.base.dto.QuestionBaseDTO;
import com.xinqi.common.base.enums.QuestionTypeEnum;
import com.xinqi.common.core.utils.StringUtils;
import com.xinqi.modules.exam.paper.domain.CourseExamPaperConfigEntity;
import com.xinqi.modules.exam.paper.enums.MultipleOptionScoreEnum;
import com.xinqi.modules.exam.student.dto.PaperAnswerDTO;
import lombok.Data;
import org.apache.commons.collections4.CollectionUtils;
import org.jetbrains.annotations.Nullable;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * 客观题打分工具类
 *
 * @author: linkai
 * @date: 2023/07/02
 */
public class MarkQuestionUtils {

    /**
     * 学生提交测评后自动批阅处理
     *
     * @param questions    试卷列表
     * @param paperAnswers 答题列表
     * @param config       配置
     * @return 打分结果
     */
    public static MarkQuestionData markStudentAnswer(List<QuestionBaseDTO> questions, List<PaperAnswerDTO> paperAnswers, CourseExamPaperConfigEntity config) {
        MarkQuestionData data = MarkQuestionData.init();
        markStudentAnswer(data, questions, paperAnswers, config);
        return data;
    }

    /**
     * 学生提交测评后自动批阅处理
     *
     * @param markQuestionData 打分结果
     * @param questions        试卷列表
     * @param paperAnswers     答题列表
     * @param config           配置
     */
    public static void markStudentAnswer(MarkQuestionData markQuestionData, List<QuestionBaseDTO> questions, List<PaperAnswerDTO> paperAnswers, CourseExamPaperConfigEntity config) {
        Map<Long, QuestionBaseDTO> questionMap = questions.stream().collect(Collectors.toMap(QuestionBaseDTO::getId, v -> v));
        for (PaperAnswerDTO paperAnswer : paperAnswers) {
            markQuestion(markQuestionData, questionMap.get(paperAnswer.getQuestionId()), paperAnswer, config);
        }
    }

    /**
     * 试题打分
     *
     * @return 打分结果
     */
    public static MarkQuestionData markQuestion(QuestionBaseDTO question, PaperAnswerDTO paperAnswer, CourseExamPaperConfigEntity config) {
        MarkQuestionData data = MarkQuestionData.init();
        markQuestion(data, question, paperAnswer, config);
        return data;
    }

    /**
     * 试题打分
     */
    public static void markQuestion(MarkQuestionData markQuestionData, QuestionBaseDTO question, PaperAnswerDTO paperAnswer, CourseExamPaperConfigEntity config) {
        QuestionTypeEnum questionType = QuestionTypeEnum.of(question.getType());
        switch (questionType) {
            case ANSWER_JUDGE:
            case ANSWER_RADIO:
                markSingle(question, paperAnswer);
                break;
            case ANSWER_MULTIPLE:
                markMultiple(question, paperAnswer, config);
                break;
            case ANSWER_INDEFINITE:
                markUncertain(question, paperAnswer);
                break;
            case ANSWER_CLOZE:
                markCloze(question, paperAnswer);
                break;
        }
        // 统计打算结果
        markQuestionData.checkObjective(question);
        markQuestionData.answer(paperAnswer.getIsRight(), paperAnswer.getStudentScore());
    }

    /**
     * 单个选项打分
     */
    private static void markSingle(QuestionBaseDTO question, PaperAnswerDTO paperAnswer) {
        List<String> rightAnswers = question.rightOptions();
        // 题目没有正确答案，全部算 0 分
        if (CollectionUtils.isEmpty(rightAnswers)) {
            paperAnswer.setIsRight(false);
            paperAnswer.setStudentScore(BigDecimal.ZERO);
            return;
        }
        // 获取正确选项
        String rightAnswer = rightAnswers.get(0);
        paperAnswer.setPrimaryStudentAnswer(rightAnswer);
        if (rightAnswer.equals(paperAnswer.getStudentAnswer())) {
            paperAnswer.setIsRight(true);
            paperAnswer.setStudentScore(question.getScore());
        } else {
            paperAnswer.setIsRight(false);
            paperAnswer.setStudentScore(BigDecimal.ZERO);
        }
    }

    /**
     * 多选题打分
     */
    private static void markMultiple(QuestionBaseDTO question, PaperAnswerDTO paperAnswer, CourseExamPaperConfigEntity config) {
        String studentAnswer = getStudentAnswer(paperAnswer);
        if (StringUtils.isBlank(studentAnswer)) {
            return;
        }

        List<String> rightAnswers = question.rightOptions();
        List<String> studentAnswers;
        if (studentAnswer.contains(",")) {
            studentAnswers = Arrays.asList(studentAnswer.split(","));
        } else {
            studentAnswers = ImmutableList.of(studentAnswer);
        }
        MultipleOptionScoreEnum multipleOptionScore = config.getMultipleOptionScore();
        switch (compareRight(rightAnswers, studentAnswers)) {
            case 1:
                paperAnswer.setIsRight(true);
                paperAnswer.setStudentScore(question.getScore());
                return;
            case 2:
                if (Objects.equals(MultipleOptionScoreEnum.少选时_得一半分数, multipleOptionScore)) {
                    paperAnswer.setIsRight(false);
                    paperAnswer.setStudentScore(question.getScore().divide(new BigDecimal(2), 1, RoundingMode.HALF_UP));
                    return;
                }
        }
        paperAnswer.setIsRight(false);
        paperAnswer.setStudentScore(BigDecimal.ZERO);
    }

    /**
     * 不定项选择题打分
     */
    private static void markUncertain(QuestionBaseDTO question, PaperAnswerDTO paperAnswer) {
        String studentAnswer = getStudentAnswer(paperAnswer);
        if (StringUtils.isBlank(studentAnswer)) {
            return;
        }

        List<String> rightAnswers = question.rightOptions();
        List<String> studentAnswers;
        if (studentAnswer.contains(",")) {
            studentAnswers = Arrays.asList(studentAnswer.split(","));
        } else {
            studentAnswers = ImmutableList.of(studentAnswer);
        }
        switch (compareRight(rightAnswers, studentAnswers)) {
            case 1:
                paperAnswer.setIsRight(true);
                paperAnswer.setStudentScore(question.getScore());
                return;
            case 2:
                paperAnswer.setIsRight(false);
                paperAnswer.setStudentScore(question.getScore().divide(new BigDecimal(rightAnswers.size()), 1, RoundingMode.HALF_UP).multiply(new BigDecimal(studentAnswers.size())));
                return;
        }
        paperAnswer.setIsRight(false);
        paperAnswer.setStudentScore(BigDecimal.ZERO);
    }

    /**
     * 完形填空题打分
     */
    private static void markCloze(QuestionBaseDTO question, PaperAnswerDTO paperAnswer) {
        String studentAnswer = getStudentAnswer(paperAnswer);
        if (StringUtils.isBlank(studentAnswer)) {
            return;
        }
        String answer = question.getAnswer();
        if (StringUtils.isBlank(answer)) {
            return;
        }
        // 将 答案使用 回车符 分割成 数组，再将数组转 为 List
        List<String> answerList = Arrays.asList(answer.split("\n"));
        List<String> studentAnswerList = Arrays.asList(studentAnswer.split("\n"));
        int i = compareAll(answerList, studentAnswerList);
        if (i == 1) {
            paperAnswer.setIsRight(true);
            paperAnswer.setStudentScore(question.getScore());
            return;
        }
        paperAnswer.setIsRight(false);
        paperAnswer.setStudentScore(BigDecimal.ZERO);
    }

    @Nullable
    private static String getStudentAnswer(PaperAnswerDTO paperAnswer) {
        String studentAnswer = paperAnswer.getStudentAnswer();
        if (StringUtils.isBlank(studentAnswer)) {
            paperAnswer.setIsRight(false);
            paperAnswer.setStudentScore(BigDecimal.ZERO);
            return null;
        }
        return studentAnswer;
    }

    /**
     * 学生答案与正确答案对比
     */
    private static int compareRight(List<String> list01, List<String> list02) {
        // 是否为空
        if (list01 == null || list02 == null) {
            return 0;
        }
        // 是否全部相等
        if (list01.size() == list02.size() && CollectionUtils.containsAll(list01, list02)) {
            return 1;
        }
        // 是否包含
        if (CollectionUtils.isSubCollection(list02, list01)) {
            return 2;
        }
        return 0;
    }

    /**
     * 学生答案与正确答案对比
     */
    private static int compareAll(List<String> list01, List<String> list02) {
        // 是否为空
        if (list01 == null || list02 == null) {
            return 0;
        }
        // 是否全部相等
        if (list01.size() == list02.size()) {
            for (int i = 0; i < list01.size(); i++) {
                if (!Objects.equals(list01.get(i), list02.get(i))) {
                    return 0;
                }
            }
            return 1;
        }
        return 0;
    }

    /**
     * 试题打分数据
     */
    @Data
    public static class MarkQuestionData {
        /**
         * 是否为全部客观题
         */
        private Boolean isAllObjective;
        /**
         * 题目答对数量
         */
        @TableField(value = "question_right_num")
        private Integer questionRightNum;
        /**
         * 得分
         */
        @TableField(value = "score")
        private BigDecimal score;

        /**
         * 校验是否客观题
         */
        public void checkObjective(QuestionBaseDTO question) {
            if (Objects.equals(this.getIsAllObjective(), Boolean.TRUE) && QuestionTypeEnum.isSubjective(question.getType())) {
                this.setIsAllObjective(false);
            }
        }

        /**
         * 答题
         */
        public void answer(boolean isRight, BigDecimal score) {
            if (isRight) {
                this.setQuestionRightNum(Optional.ofNullable(this.getQuestionRightNum()).orElse(0) + 1);
            }
            if (score != null && score.compareTo(BigDecimal.ZERO) > 0) {
                this.setScore(Optional.ofNullable(this.getScore()).orElse(BigDecimal.ZERO).add(score));
            }
        }

        /**
         * 初始化
         */
        public static MarkQuestionData init() {
            MarkQuestionData data = new MarkQuestionData();
            data.setIsAllObjective(true);
            data.setQuestionRightNum(0);
            data.setScore(BigDecimal.ZERO);
            return data;
        }
    }
}
